#define FAKE_WEBCAM
//#define TRANSFORM_DEBUG
//#define RESCALE

/* 
 * Capture Test NyARToolkitCSサンプルプログラム
 * --------------------------------------------------------------------------------
 * The MIT License
 * Copyright (c) 2008 nyatla
 * airmail(at)ebony.plala.or.jp
 * http://nyatla.jp/nyartoolkit/
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * 
 */
using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using D3D = Microsoft.DirectX.Direct3D;
using NyARToolkitCSUtils;
using NyARToolkitCSUtils.Capture;
using NyARToolkitCSUtils.Direct3d;
using jp.nyatla.nyartoolkit.cs.core;
using jp.nyatla.nyartoolkit.cs.markersystem;
using SimpleLiteForm.Translator;
using SimpleLiteForm.Graphics;
using SimpleLiteForm.Storage;
using SimpleLiteForm.References;
using SimpleLiteForm.Text;

namespace SimpleLiteForm
{
    /// <summary>
    /// The live mode state.
    /// </summary>
    public enum LiveModeState
    {
        Normal,
        Drawing,
    }

    /// <summary>
    /// The live view panel wrapper with Direct3D and NyARToolkit.
    /// </summary>
    public class LiveViewManager : IDisposable
    {
        /// <summary>
        /// The window width.
        /// </summary>
        private const int SCREEN_WIDTH = 640;

        /// <summary>
        /// The windows width.
        /// </summary>
        private const int SCREEN_HEIGHT = 480;

        /// <summary>
        /// The number of frames per OCR run.
        /// 1 = 100%, 2 = 50%, 4 = 25%, ..., 20 = 5%, ...
        /// </summary>
        public const int OCR_RATIO = 20;

        /// <summary>
        /// The main window form.
        /// </summary>
        private Form1 _mainForm;

        /// <summary>
        /// The frame counter.
        /// </summary>
        private int _frameCounter = -1;

        /// <summary>
        /// The presentation orientation marker.
        /// </summary>
        private const String AR_CODE_FILE = "../../../../../data/0.pat";

        /// <summary>
        /// The drawing surface.
        /// </summary>
        private NyARD3dSurface _surface;
        //private ColorCube _cube;

        /// <summary>
        /// The marker system.
        /// </summary>
        private NyARD3dMarkerSystem _ms;

        /// <summary>
        /// The camera.
        /// </summary>
        private NyARDirectShowCamera _ss;

        /// <summary>
        /// The DirectX rendering device.
        /// </summary>
        private Device _d3d;

        /// <summary>
        /// The ID of the detected presentation marker.
        /// </summary>
        private int mid;

        /// <summary>
        /// The OCR manager.
        /// </summary>
        OcrManager _ocrManager;

        /// <summary>
        /// Used to render lines.
        /// </summary>
        private D3D.Line _line;

        /// <summary>
        /// Used to render 3D text.
        /// </summary>
        private Sprite _sprite;

        /// <summary>
        /// Indicates wheter the 3D mode is active.
        /// </summary>
        private const bool IS_TEXT_3D = true;

        /// <summary>
        /// The faked webcam image raster in high quality for testing purposes.
        /// </summary>
        private NyARBitmapRaster _fakeWebcamImageRaster;

        /// <summary>
        /// The control to render and click on.
        /// </summary>
        private Control _control;

        /// <summary>
        /// The popup control.
        /// </summary>
        private InfoPopup _popup;

        /// <summary>
        /// The pre-generated box for the darken unselected region.
        /// </summary>
        private RenderObject _ocrBox;

        private RenderObject _wordBackgroundBox;

        /// <summary>
        /// The drawing manager.
        /// </summary>
        DrawingManager _drawingManager;

        /// <summary>
        /// Indicates whether the drawing mode is active.
        /// </summary>
        private LiveModeState _state = LiveModeState.Normal;

        /// <summary>
        /// Creates a LiveViewManager instance.
        /// </summary>
        /// <param name="control">The control to render on.</param>
        public LiveViewManager(Form1 mainForm, Control control)
        {
            _mainForm = mainForm;

            //setup camera
            Program.capture_device.PrepareCapture(SCREEN_WIDTH, SCREEN_HEIGHT, 30.0f);

            //setup form
            _control = control;
            control.ClientSize = new Size(SCREEN_WIDTH, SCREEN_HEIGHT);
            control.MouseDown += FormMouseDownOCR;
            control.MouseDown += FormMouseDownButtons;
            control.MouseMove += FormMouseMoveOCR;
            control.MouseMove += FormMouseMoveButtons;
            control.MouseUp += FormMouseUp;
            control.MouseLeave += FormMouseLeave;

            //setup AR
            INyARMarkerSystemConfig cf = new NyARMarkerSystemConfig(SCREEN_WIDTH, SCREEN_HEIGHT);
            this._ms = new NyARD3dMarkerSystem(cf);
            this._ss = new NyARDirectShowCamera(Program.capture_device);
            this.mid = this._ms.addARMarker(AR_CODE_FILE, 16, 25, 80);

            // SETUP DIRECT X
            // 3d
            this._d3d = NyARD3dUtil.createD3dDevice(control);
            this._d3d.RenderState.ZBufferEnable = true;
            this._d3d.RenderState.Lighting = false;
            // view
            this._d3d.Transform.View = NyARD3dUtil.getARView();
            this._d3d.Viewport = NyARD3dUtil.getARViewPort(SCREEN_WIDTH, SCREEN_HEIGHT);
            // projection
            this._ms.setProjectionMatrixClipping(10, 10000);
            Matrix pm = new Matrix();
            NyARD3dUtil.toCameraFrustumRH(this._ms.getARParam(), 10, 10000, ref pm);
            this._d3d.Transform.Projection = pm;

            // rendering surface
            this._surface = new NyARD3dSurface(this._d3d, SCREEN_WIDTH, SCREEN_HEIGHT);

            _ocrManager = new OcrManager(SCREEN_WIDTH, SCREEN_HEIGHT);

            _popup = new InfoPopup(_d3d);

            _drawingManager = new DrawingManager(_d3d, _control);

            FontHelper.Initialize(_d3d);
            InitializeBox();
            InitializeOcrBox();
            InitializeWordBackgroundBox();
            InitializeSprite();
            InitializeButtons();
            
#if FAKE_WEBCAM
            InitializeFakeWebcam();
#endif
        }

        /// <summary>
        /// Initializes the lines for the boxes.
        /// </summary>
        private void InitializeBox()
        {
            _line = new D3D.Line(_d3d);      
        }

        /// <summary>
        /// Initializes the OCR rect box.
        /// </summary>
        private void InitializeOcrBox()
        {
            _ocrBox = new RenderObject(_d3d, "../../assets/textures/placeholder.jpg");
            _ocrBox.Color = Color.FromArgb(128, Color.Black);
            _ocrBox.Position = new PointF(-100, -100);
            _ocrBox.Size = new SizeF(10, 10);
        }

        private void InitializeWordBackgroundBox()
        {
            _wordBackgroundBox = new RenderObject(_d3d, "../../assets/textures/placeholder.jpg");
            _wordBackgroundBox.Color = Color.FromArgb(225, Color.Black);
        }

        /// <summary>
        /// Initializes the sprite.
        /// </summary>
        private void InitializeSprite()
        {
            _sprite = new Sprite(_d3d);
        }

        /// <summary>
        /// Initializes the fake webcam image.
        /// </summary>
        private void InitializeFakeWebcam()
        {
            Bitmap bmp = new Bitmap("../../assets/slides/slides6.jpg");
            _fakeWebcamImageRaster = new NyARBitmapRaster(bmp);
        }

        /// <summary>
        /// Updates the live view.
        /// </summary>
        public void Update()
        {
            ++_frameCounter;

            lock (this._ss)
            {
#if FAKE_WEBCAM
                // use faked webcam image
                _ss.update(_fakeWebcamImageRaster);
#endif
                // update marker system
                this._ms.update(this._ss);

                // webcam image
                this._surface.setRaster(this._ss.getSourceImage());
                Surface dest_surface = this._d3d.GetBackBuffer(0, 0, BackBufferType.Mono);
                Rectangle rect = new Rectangle(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
                this._d3d.StretchRectangle((Surface)this._surface, rect, dest_surface, rect, TextureFilter.None);
                // 3D rendering
                this._d3d.BeginScene();
                this._d3d.Clear(ClearFlags.ZBuffer, Color.DarkBlue, 1.0f, 0);
                /*** OCR ***/
                NyARBitmapRaster ra = (NyARBitmapRaster)this._ss.getSourceImage();
                Bitmap image = new Bitmap(ra.getBitmap());
         
                if (_ocrManager.IsRectangleForOcrValid &&
                    this._ms.isExistMarker(mid) &&
                    !_popup.IsActive &&
                    _state == LiveModeState.Normal)
                {
                    if (_frameCounter % OCR_RATIO == 0 && !IsAnimationActive && !_isMouseSelectionActive)
                        _ocrManager.Update(image);

                    // marker translation
                    Matrix transform_mat2;
                    transform_mat2 = Matrix.RotationX(135);
                    transform_mat2 *= this._ms.getD3dMarkerMatrix(this.mid);
                    
                    _sprite.Begin(SpriteFlags.ObjectSpace | SpriteFlags.AlphaBlend);
                    _sprite.Transform = transform_mat2;

                    //get marker screen pos
                    Vector2 markerPos = new Vector2();
                    this._ms.getScreenPos(this.mid, 0, 0, 0, ref markerPos);
                    NyARFrustum.FrustumParam fp = new NyARFrustum.FrustumParam();
                    fp = this._ms.getFrustum().getFrustumParam(fp);
#if TRANSFORM_DEBUG
/*******************************transform test begin***************************************************************************/
                    string testWord = "Test Word + Test Word";
                    Rectangle textRect = FontHelper.TextNormal.MeasureString(_sprite, testWord, DrawTextFormat.None, Color.Red);
                    FontHelper.TextNormal.DrawText(_sprite, testWord, new Point(-textRect.Width / 2, -textRect.Height / 2), Color.Red);

                    _wordBackgroundBox.Size = new SizeF(textRect.Width, textRect.Height);
                    _wordBackgroundBox.Position = new PointF(-textRect.Width / 2, -textRect.Height / 2);
                    _wordBackgroundBox.setTransform(transform_mat2);
                    _wordBackgroundBox.Color = Color.FromArgb(127, Color.Blue);

                    _wordBackgroundBox.Draw3D();

/*******************************transform test end*******************************************************************************/
#endif
                    List<tessnet2.Word> words;

                    if (IsFixedTranslationActive)
                    {
                        if (_preparedTranslatedWordList.Count == 0)
                        {
                            words = new List<tessnet2.Word>();

                            foreach (var word in _ocrManager.FilteredResult)
                            {
                                // copy (just the used stuff for optimization)
                                var w = new tessnet2.Word();
                                w.Bottom = word.Bottom;
                                w.Left = word.Left;
                                w.Top = word.Top;
                                w.Right = word.Right;
                                w.PointSize = word.PointSize;
                                w.Confidence = word.Confidence;
                                // translate
                                w.Text = BingTranslationService.Instance.Translate(word.Text, Settings.PreferedLanguage);
                                words.Add(w);
                            }

                            _preparedTranslatedWordList = words;
                        }
                        else
                        {
                            words = _preparedTranslatedWordList;
                        }
                    }
                    else
                    {
                        words = _ocrManager.FilteredResult;
                    }

                    RenderWordTexts(_sprite, words, fp);
                    RenderWordBoxes(words);

                    _sprite.End(); 
                }

                _popup.Draw();

                _drawingManager.Draw();

                // do a screenshot
                if (doScreenshot)
                {
                    string screenshot = IOHelper.SaveScreenshot(dest_surface);

                    //add the path to presentation
                    _mainForm.getPresentationManager().addScreenshotPath(screenshot);

                    if (!string.IsNullOrEmpty(screenshot))
                    {
                        Bitmap bmp = IOHelper.LoadImage(screenshot);

                        if (_screenshotObject != null)
                            _screenshotObject.Dispose();

                        _screenshotObject = new RenderObject(_d3d, screenshot);
                        _screenshotObject.Size = new SizeF(_control.ClientSize.Width, _control.ClientSize.Height);
                    }
                    
                    doScreenshot = false;
                }

                RenderRectangleForOcr();

                UpdateAndDrawScreenshotAnimation();

                // buttons on the right side of the screen
                UpdateButtons();
                DrawButtons();

                --_fixedTranslationCountDown;

                if (_fixedTranslationCountDown > 0)
                {
                    FontHelper.TextNormal.DrawText(null, "Loading...", new Point(275, 10), Color.White);
                }

                this._d3d.EndScene();

                this._d3d.Present();

                // cleanup for this frame
                image.Dispose();
            }
        }

        /// <summary>
        /// The screenshot render object for the screenshot animation.
        /// </summary>
        private RenderObject _screenshotObject = null;

        /// <summary>
        /// Indicates whether an animation is active.
        /// </summary>
        public bool IsAnimationActive
        {
            get
            {
                return _screenshotObject != null || _isButtonAnimationActive;
            }
        }

        /// <summary>
        /// Updates and draws the screenshot animation.
        /// </summary>
        private void UpdateAndDrawScreenshotAnimation()
        {
            if (_screenshotObject == null)
                return;

            var oldSize = _screenshotObject.Size;

            // resize and move
            _screenshotObject.Size = new SizeF(oldSize.Width - 80, oldSize.Height - 60);
            _screenshotObject.Move(40, 30);

            // verify animation is over
            if (oldSize.Width < 80 || oldSize.Height < 80)
            {
                _screenshotObject.Dispose();
                _screenshotObject = null;
                return;
            }

            // draw
            _screenshotObject.Draw();
        }

        /// <summary>
        /// Clean up the resources.
        /// </summary>
        public void Dispose()
        {
            lock (this._ss)
            {
                if (this._surface != null)
                {
                    this._surface.Dispose();
                }
                if (this._d3d != null)
                {
                    this._d3d.Dispose();
                }
                if (this._popup != null)
                {
                    this._popup.Dispose();
                }
                if (_ocrManager != null)
                {
                    _ocrManager.Dispose();
                }
                if (_ocrBox != null)
                {
                    _ocrBox.Dispose();
                }
                if (_drawingManager != null)
                {
                    _drawingManager.Dispose();
                }
                if (_screenshotObject != null)
                {
                    _screenshotObject.Dispose();
                }
                if (_line != null)
                {
                    _line.Dispose();
                }
                if (_sprite != null)
                {
                    _sprite.Dispose();
                }
                if (_wordBackgroundBox != null)
                {
                    _wordBackgroundBox.Dispose();
                }

                DisposeButtons();
            }
        }

        #region OCR

        /// <summary>
        /// Renders the word boxes in 2D.
        /// </summary>
        /// <param name="wordList"></param>
        private void RenderWordBoxes(List<tessnet2.Word> wordList)
        {
            Color borderColor = Color.FromArgb(128, Color.Red);
            Rectangle ocrRect = _ocrManager.RectangleForOcr;

            foreach (tessnet2.Word word in wordList)
            {
                if (IS_TEXT_3D)
                {
                    //temperarily 3d boxes are together with 3d texts! >_<
                }
                else
                {
                    Vector2[] vertexList = new Vector2[5];
                    vertexList[0] = new Vector2(ocrRect.X + word.Left, ocrRect.Y + word.Top);
                    vertexList[1] = new Vector2(ocrRect.X + word.Right, ocrRect.Y + word.Top);
                    vertexList[2] = new Vector2(ocrRect.X + word.Right, ocrRect.Y + word.Bottom);
                    vertexList[3] = new Vector2(ocrRect.X + word.Left, ocrRect.Y + word.Bottom);
                    vertexList[4] = vertexList[0];

                    _line.Draw(vertexList, borderColor);
                }
                
            }
        }

        /// <summary>
        /// Renders the word texts.
        /// </summary>
        /// <param name="spr">The sprite</param>
        /// <param name="wordList">The word list.</param>
        private void RenderWordTexts(Sprite spr, List<tessnet2.Word> wordList, NyARFrustum.FrustumParam frustum)
        {
            //get marker info
            Matrix markerCTM = this._ms.getD3dMarkerMatrix(this.mid);
            Vector3 markerWorldPos = new Vector3(markerCTM.M41,markerCTM.M42,markerCTM.M43);
            Vector2 markerScreenPos = new Vector2();
            this._ms.getScreenPos(this.mid, 0, 0, 0, ref markerScreenPos);
            float factor = 1.0f;
            Vector3 pos = new Vector3();
            float width = (float)(frustum.right - frustum.left);
            float height = (float)(frustum.top - frustum.bottom);
            float scaleFactor = 352.4156f / markerWorldPos.Z + 1.324f;

            //texts
            using (Sprite sprt = new Sprite(_d3d))
            {
                sprt.Begin(SpriteFlags.ObjectSpace | SpriteFlags.AlphaBlend);

                foreach (tessnet2.Word word in wordList)
                {
                    Rectangle textRect = FontHelper.TextNormal.MeasureString(spr, word.Text, DrawTextFormat.None, Color.White);
                    Point ocrCenter = new Point((word.Left + word.Right) / 2, (word.Top + word.Bottom) / 2);
                    Rectangle ocrRect = _ocrManager.RectangleForOcr;

#if RESCALE
                float newScaleFactor = scaleFactor * (word.Bottom - word.Top) * 0.06f;
#else
                    float newScaleFactor = scaleFactor;
#endif

                    // 3D Text & boxes
                    if (IS_TEXT_3D)
                    {
                        float transX = (ocrRect.X + ocrCenter.X - markerScreenPos.X) * width / SCREEN_WIDTH * markerWorldPos.Z / 10 * factor;
                        float transY = (ocrRect.Y + ocrCenter.Y - markerScreenPos.Y) * height / SCREEN_HEIGHT * markerWorldPos.Z / 10 * factor;

                        //boxes
                        _wordBackgroundBox.Size = new SizeF(textRect.Width, textRect.Height);
                        _wordBackgroundBox.Position = new PointF(-textRect.Width / 2, -textRect.Height / 2);
                        _wordBackgroundBox.setTransform(Matrix.Scaling(newScaleFactor, newScaleFactor, newScaleFactor) *
                            spr.Transform *
                            Matrix.Translation(-transX, transY, 0));
                        _wordBackgroundBox.Draw3D();//draw box

                        sprt.Transform = Matrix.Scaling(newScaleFactor, newScaleFactor, newScaleFactor) *
                            spr.Transform *
                            Matrix.Translation(-transX, transY, 0);

                        FontHelper.TextNormal.DrawText(sprt, word.Text,
                            new Point(-textRect.Width / 2, -textRect.Height / 2),
                            Color.White);
                    }

                    // 2D text 
                    else
                    {
                        FontHelper.TextNormal.DrawText(null, word.Text,
                            new Point(ocrRect.X + ocrCenter.X - textRect.Width / 2,
                            ocrRect.Y + ocrCenter.Y - textRect.Height / 2),
                            Color.White);
                    }
                }

                sprt.End();
            }
        }

        #endregion

        #region Screen OCR selection

        /// <summary>
        /// Renders the selected OCR region.
        /// </summary>
        private void RenderRectangleForOcr()
        {
            if (!_ocrManager.IsRectangleForOcrValid ||
                _state == LiveModeState.Drawing ||
                _popup.IsActive)
                return;

            Rectangle ocrRect = _ocrManager.RectangleForOcr;

            // top
            _ocrBox.Position = new PointF(0, 0);
            _ocrBox.Size = new SizeF(SCREEN_WIDTH, ocrRect.Top);
            _ocrBox.Draw();

            // bottom
            _ocrBox.Position = new PointF(0, ocrRect.Bottom);
            _ocrBox.Size = new SizeF(SCREEN_WIDTH, SCREEN_HEIGHT - ocrRect.Bottom);
            _ocrBox.Draw();

            // left
            _ocrBox.Position = new PointF(0, ocrRect.Top);
            _ocrBox.Size = new SizeF(ocrRect.Left, ocrRect.Bottom - ocrRect.Top);
            _ocrBox.Draw();

            // right
            _ocrBox.Position = new PointF(ocrRect.Right, ocrRect.Top);
            _ocrBox.Size = new SizeF(SCREEN_WIDTH - ocrRect.Right, ocrRect.Bottom - ocrRect.Top);
            _ocrBox.Draw();
        }

        /// <summary>
        /// Indicates whether the OCR region selection is active.
        /// </summary>
        bool _isMouseSelectionActive = false;

        /// <summary>
        /// The points to draw.
        /// </summary>
        private IList<Point> _wordSelectionPointList = new List<Point>();
        private bool _isWordSelectionActive = false;


        /// <summary>
        /// Handles mouse down event on the form.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event args.</param>
        private void FormMouseDownOCR(object sender, MouseEventArgs e)
        {
            if (_state == LiveModeState.Normal)
            {
                if (_popup.IsActive)
                {
                    _popup.HandleClick(e.Location);
                    return;
                }

                if (e.Button == MouseButtons.Right)
                {
                    _ocrManager.StartPosition = e.Location;
                    _ocrManager.EndPosition = e.Location;
                    _ocrManager.ClearResults();

                    // reset fixet translate struff
                    if (IsFixedTranslationActive)
                    {
                        _preparedTranslatedWordList.Clear();
                        _fixedTranslationCountDown = FIXED_TRANS_COUNT_LIMIT;
                    }

                    _isMouseSelectionActive = true;
                }
                else
                {
                    /*Rectangle ocrRect = _ocrManager.RectangleForOcr;

                    IEnumerable<tessnet2.Word> wordListCopy = new List<tessnet2.Word>(_ocrManager.FilteredResult);

                    foreach (var word in wordListCopy)
                    {
                        Rectangle wordRect = new Rectangle(ocrRect.X + word.Left, ocrRect.Y + word.Top,
                                                            word.Right - word.Left, word.Bottom - word.Top);
                        if (wordRect.Contains(e.Location))
                        {
                            // show translation
                            string translation = BingTranslationService.Instance.Translate(word.Text, Settings.PreferedLanguage);
                            _popup.Show(word.Text, translation);

                            break;
                        }
                    }*/

                    if (_ocrManager.IsRectangleForOcrValid &&
                        _ocrManager.RectangleForOcr.Contains(e.Location))
                    {
                        _isWordSelectionActive = true;
                        _wordSelectionPointList.Clear();
                        _wordSelectionPointList.Add(e.Location);
                    }
                }
            }
        }

        /// <summary>
        /// Updates the selected OCR region when the mouse position changes.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event args.</param>
        private void FormMouseMoveOCR(object sender, MouseEventArgs e)
        {
            if (_state == LiveModeState.Normal)
            {
                if (_isMouseSelectionActive)
                {
                    _ocrManager.EndPosition = e.Location;
                }

                if (_isWordSelectionActive)
                {
                    if (_ocrManager.RectangleForOcr.Contains(e.Location))
                    {
                        _wordSelectionPointList.Add(e.Location);
                    }
                }
            }
        }

        /// <summary>
        /// Deactivates the mouse selection when the mouse is not pressed anymore.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event args.</param>
        private void FormMouseUp(object sender, MouseEventArgs e)
        {
            if (_state == LiveModeState.Normal)
            {
                if (e.Button == MouseButtons.Right)
                {
                    _ocrManager.EndPosition = e.Location;
                    _isMouseSelectionActive = false;

                }

                if (_isWordSelectionActive)
                {
                    _isWordSelectionActive = false;

                    Rectangle ocrRect = _ocrManager.RectangleForOcr;

                    IEnumerable<tessnet2.Word> wordListCopy = new List<tessnet2.Word>(_ocrManager.FilteredResult);

                    IList<string> combinedWordList = new List<string>();

                    foreach (var selectionPoint in _wordSelectionPointList)
                    {
                        foreach (var word in wordListCopy)
                        {
                            Rectangle wordRect = new Rectangle(ocrRect.X + word.Left, ocrRect.Y + word.Top,
                                                                word.Right - word.Left, word.Bottom - word.Top);
                            if (wordRect.Contains(selectionPoint))
                            {
                                // verfy not already added
                                if (combinedWordList.Count == 0 || combinedWordList[combinedWordList.Count - 1] != word.Text)
                                    combinedWordList.Add(word.Text);
                            }
                        }
                    }

                    string combinedWord = string.Empty;

                    for (int i = 0; i < combinedWordList.Count; ++i)
                    {
                        if (i != 0)
                            combinedWord += " ";

                        combinedWord += combinedWordList[i];
                    }

                    if (!string.IsNullOrEmpty(combinedWord))
                    {

                        // show translation
                        string translation = BingTranslationService.Instance.Translate(combinedWord, Settings.PreferedLanguage);
                        _popup.Show(combinedWord, translation);
                    }
                }
            }
        }

        /// <summary>
        /// Disables the region selection when the cursor leaves the screen.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event args.</param>
        private void FormMouseLeave(object sender, EventArgs e)
        {
            _isMouseSelectionActive = false;
        }

        #endregion

        #region Buttons

        private RenderObject _homeButton;
        private RenderObject _screenshotButton;
        private RenderObject _translationToggleOnButton;
        private RenderObject _translationToggleOffButton;
        private RenderObject _drawingButton;
        private RenderObject _clearButton;
        private RenderObject _endDrawingButton;

        private const int BUTTON_BOTTOM_MIN_Y = SCREEN_HEIGHT - 50;
        private const int BUTTON_BOTTOM_MAX_Y = SCREEN_HEIGHT - 15;

        /// <summary>
        /// Indicates whether the buttons are displayed or hidden.
        /// </summary>
        private bool _buttonsVisible = false;

        /// <summary>
        /// Initializes the buttons.
        /// </summary>
        private void InitializeButtons()
        {
            _homeButton = new RenderObject(_d3d, "../../assets/textures/save_done.png");
            _homeButton.Size = new SizeF(50, 50);
            _homeButton.Position = new PointF(145, BUTTON_BOTTOM_MAX_Y);

            _screenshotButton = new RenderObject(_d3d, "../../assets/textures/screenshot.png");
            _screenshotButton.Size = new SizeF(50, 50);
            _screenshotButton.Position = new PointF(245, BUTTON_BOTTOM_MAX_Y);

            _translationToggleOnButton = new RenderObject(_d3d, "../../assets/textures/translate.png");
            _translationToggleOnButton.Size = new SizeF(50, 50);
            _translationToggleOnButton.Position = new PointF(345, BUTTON_BOTTOM_MAX_Y);

            _translationToggleOffButton = new RenderObject(_d3d, "../../assets/textures/translate_off.png");
            _translationToggleOffButton.Size = new SizeF(50, 50);
            _translationToggleOffButton.Position = new PointF(345, BUTTON_BOTTOM_MAX_Y);

            _drawingButton = new RenderObject(_d3d, "../../assets/textures/draw.png");
            _drawingButton.Size = new SizeF(50, 50);
            _drawingButton.Position = new PointF(445, BUTTON_BOTTOM_MAX_Y);

            _clearButton = new RenderObject(_d3d, "../../assets/textures/delete.png");
            _clearButton.Size = new SizeF(50, 50);
            _clearButton.Position = new PointF(245, BUTTON_BOTTOM_MAX_Y);

            _endDrawingButton = new RenderObject(_d3d, "../../assets/textures/close.png");
            _endDrawingButton.Size = new SizeF(50, 50);
            _endDrawingButton.Position = new PointF(345, BUTTON_BOTTOM_MAX_Y);
        }

        /// <summary>
        /// Indicates whether the button animation is currently active.
        /// </summary>
        private bool _isButtonAnimationActive = false;

        /// <summary>
        /// Updates the button position.
        /// </summary>
        private void UpdateButtons()
        {
            int offset = 10;

            if (_homeButton.Position.Y >= BUTTON_BOTTOM_MIN_Y && _buttonsVisible)
            {
                _homeButton.Move(0, -offset);
                _screenshotButton.Move(0, -offset);
                _translationToggleOnButton.Move(0, -offset);
                _translationToggleOffButton.Move(0, -offset);
                _drawingButton.Move(0, -offset);
                _clearButton.Move(0, -offset);
                _endDrawingButton.Move(0, -offset);
                _isButtonAnimationActive = true;
            }
            else if (_homeButton.Position.Y <= BUTTON_BOTTOM_MAX_Y && !_buttonsVisible)
            {
                _homeButton.Move(0, offset);
                _screenshotButton.Move(0, offset);
                _translationToggleOnButton.Move(0, offset);
                _translationToggleOffButton.Move(0, offset);
                _drawingButton.Move(0, offset);
                _clearButton.Move(0, offset);
                _endDrawingButton.Move(0, offset);
                _isButtonAnimationActive = true;
            }
            else
            {
                _isButtonAnimationActive = false;
            }
        }

        /// <summary>
        /// Renders the buttons.
        /// </summary>
        private void DrawButtons()
        {
            if (_state == LiveModeState.Normal)
            {
                _homeButton.Draw();
                _screenshotButton.Draw();

                if (IsFixedTranslationActive)
                    _translationToggleOffButton.Draw();
                else
                    _translationToggleOnButton.Draw();

                _drawingButton.Draw();
            }
            else if (_state == LiveModeState.Drawing)
            {
                _clearButton.Draw();
                _endDrawingButton.Draw();
            }
        }

        /// <summary>
        /// Disposes the buttons.
        /// </summary>
        private void DisposeButtons()
        {
            if (_homeButton != null)
            {
                _homeButton.Dispose();
                _screenshotButton.Dispose();
                _translationToggleOnButton.Dispose();
                _translationToggleOffButton.Dispose();
                _drawingButton.Dispose();
                _clearButton.Dispose();
                _endDrawingButton.Dispose();
            }
        }

        /// <summary>
        /// Indicates that the user wants to do a screenshot.
        /// </summary>
        private bool doScreenshot = false;

        /// <summary>
        /// Fires the button click actions..
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event args.</param>
        private void FormMouseDownButtons(object sender, MouseEventArgs e)
        {
            if (_state == LiveModeState.Normal)
            {
                if (_homeButton.Rectangle.Contains(e.Location))
                {
                    _mainForm.SelectApplicationState(ApplicationState.Start);
                }
                else if (_screenshotButton.Rectangle.Contains(e.Location))
                {
                    // set the flag that the user wants to do a screenshot in the next frame.
                    doScreenshot = true;
                }
                else if (_translationToggleOnButton.Rectangle.Contains(e.Location) ||
                    _translationToggleOffButton.Rectangle.Contains(e.Location))
                {
                    ToggleFixedTranslation();
                }
                else if (_drawingButton.Rectangle.Contains(e.Location))
                {
                    _state = LiveModeState.Drawing;
                    _drawingManager.IsEditingEnabled = true;
                }
                _mainForm.getPresentationManager().endCurrentPresentation();
            }
            else if (_state == LiveModeState.Drawing)
            {
                if (_clearButton.Rectangle.Contains(e.Location))
                {
                    _drawingManager.Clear();
                }
                else if (_endDrawingButton.Rectangle.Contains(e.Location))
                {
                    _state = LiveModeState.Normal;
                    _drawingManager.IsEditingEnabled = false;
                }
            }
        }

        /// <summary>
        /// Shows or hides the buttons depending on where the mouse is located.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The even args.</param>
        private void FormMouseMoveButtons(object sender, MouseEventArgs e)
        {
            if (e.Location.Y > SCREEN_HEIGHT - 75)
                _buttonsVisible = true;
            else
                _buttonsVisible = false;
        }

        #endregion

        #region Translated fixed image

        /// <summary>
        /// Indicates whether the fixed camera is active.
        /// </summary>
        private bool _isFixedTranslationActive = false;

        /// <summary>
        /// Countdown to activate the translation mode, because it takes
        /// a while until the webcam is activated/deactivated.
        /// </summary>
        private int _fixedTranslationCountDown;

        /// <summary>
        /// The countdown reset value.
        /// </summary>
        private const int FIXED_TRANS_COUNT_LIMIT = 45;

        /// <summary>
        /// The prepared translated word list for reuse and just a single translation try.
        /// </summary>
        private List<tessnet2.Word> _preparedTranslatedWordList = new List<tessnet2.Word>();

        /// <summary>
        /// Toggles the fixed translation view.
        /// </summary>
        public void ToggleFixedTranslation()
        {
            if (!_ocrManager.IsRectangleForOcrValid && !IsFixedTranslationActive)
                return;

            IsFixedTranslationActive = !IsFixedTranslationActive;
        }

        /// <summary>
        /// Activates or deactivates the fixed translated camera mode.
        /// </summary>
        public bool IsFixedTranslationActive
        {
            get
            {
                return _isFixedTranslationActive && _fixedTranslationCountDown <= 0;
            }
            set
            {
                if (_isFixedTranslationActive != value)
                {
                    _isFixedTranslationActive = value;

                    // clear the pre generated words
                    _preparedTranslatedWordList.Clear();
                    
                    // note: no lock(_ss) here -> caused deadlocks...
                    if (_isFixedTranslationActive)
                        _ss.stop();
                    else
                        _ss.start();

                    // reset countdown
                    _fixedTranslationCountDown = FIXED_TRANS_COUNT_LIMIT;
                }
            }
        }


        #endregion
    }
}
